# -*- coding: UTF-8 -*-
"""
@Project ：swift-forge-engine 
@File    ：auth_service
@IDE     ：PyCharm 
@Author  ：Tony
@Date    ：2025/2/20 15:36 
@Des     ：
"""
import json

from fastapi import Request, Response, BackgroundTasks
from fastapi.security import HTTPBasicCredentials
from jose import jwt
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.background import BackgroundTask

from App.app.admin.crud.sys_user_crud import SysUserDao
from App.app.admin.models.sys_user import SysUser
from App.app.admin.schemas.sys_user_schema import AuthSchemaBase, GetSysUseInfoNoRoleDetail
from App.app.admin.schemas.token import GetLoginToken, GetNewToken
from App.app.admin.services.login_log_service import LoginLogService
from App.common.exception.errors import CustomError, NotFoundError, AuthorizationError, ForbiddenError
from App.common.security.jwt import password_verify, create_access_token, get_token, create_refresh_token, jwt_decode, \
    create_new_token
from App.config.conf import settings
from App.config.log_conf import log
from App.database.mysql import async_db_session
from App.database.redis import redis_client
from App.utils.enums import LoginLogStatusType
from App.utils.timezone import timezone


class AuthServices:
    @staticmethod
    async def user_verify(db: AsyncSession, username: str, password: str) -> SysUser:
        user = await SysUserDao.get_by_username(db, username=username)
        if not user:
            raise NotFoundError(message='用户名或密码有误')
        elif not password_verify(password, str(user.password)):
            raise AuthorizationError(message='用户名或密码有误')
        elif user.status == 0:
            raise AuthorizationError(message='用户已被锁定, 请联系统管理员')
        return user

    async def swagger_login(self, *, obj_in: HTTPBasicCredentials) -> tuple[str, SysUser]:
        async with async_db_session.begin() as db:
            user = await self.user_verify(db, obj_in.username, obj_in.password)
            await SysUserDao.update_login_time(db, obj_in.username)
            a_token = await create_access_token(
                str(user.id),
                user.is_multi_login,
                login_type='swagger',
            )
            return a_token.access_token, user

    @staticmethod
    async def logout(*, request: Request, response: Response):
        try:
            # 从请求头获取访问令牌
            token = get_token(request)
            if not token:
                return
            # 解码令牌获取关键信息
            payload = jwt.decode(
                token,
                settings.TOKEN_SECRET_KEY,
                algorithms=[settings.TOKEN_ALGORITHM]
            )
            user_id = payload["sub"]
            cache_user = await redis_client.get(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}')
            if cache_user:
                cache_user = json.loads(cache_user)
                if cache_user.get("is_multi_login", 0):  # 如果允许多设备登录，仅删除当前TOKEN和缓存的用户信息
                    key = f'{settings.TOKEN_REDIS_PREFIX}:{user_id}:{token}'
                    await redis_client.delete(key)
                    key = f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}'
                    await redis_client.delete(key)
                else:  # 如果不允许多设备登录，删除和当前用户相关的所有redis
                    key_prefix = f'{settings.TOKEN_REDIS_PREFIX}:{user_id}:'
                    await redis_client.delete_prefix(key_prefix)
                    key_prefix = f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}:'
                    await redis_client.delete_prefix(key_prefix)
                    key = f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}'
                    await redis_client.delete(key)

        except Exception as e:
            log.error(f"退出异常: {str(e)}")
            return

    async def login(
            self, *, request: Request, response: Response, obj_in: AuthSchemaBase, background_tasks: BackgroundTasks
    ) -> GetLoginToken:
        async with async_db_session.begin() as db:
            user = None
            try:
                user = await self.user_verify(db, obj_in.username, obj_in.password)
                # 如果有需要作验证码验证，可以在这一步之后验证，可以是手机验证码也可以是图形验证码，本框架暂时不考虑验证码验证逻辑
                """
                可以在此处增加验证码验证逻辑
                """
                await SysUserDao.update_login_time(db, obj_in.username)
                await db.refresh(user)
                a_token = await create_access_token(
                    str(user.id),
                    user.is_multi_login,

                    # Token 附加信息
                    username=user.username,
                    nickname=user.nickname,
                    last_login_time=timezone.t_str(user.last_login_time),
                    ip=request.state.ip,
                    os=request.state.os,
                    browser=request.state.browser,
                    device=request.state.device,
                )
                r_token = await create_refresh_token(str(user.id), user.is_multi_login)
            except NotFoundError as e:
                log.error('登陆错误: 用户名不存在')
                raise NotFoundError(message="登陆错误: 用户名不存在")
            except (AuthorizationError, CustomError) as e:
                if not user:
                    log.error('登陆错误: 用户密码有误')
                task = BackgroundTask(
                    LoginLogService.create,
                    **dict(
                        db=db,
                        request=request,
                        user_id=user.id if user else -1,
                        username=obj_in.username,
                        login_time=timezone.now(),
                        status=LoginLogStatusType.fail.value,
                        message=e.message,
                    ),
                )
                raise AuthorizationError(message=e.message, background=task)
            except Exception as e:
                log.error(f'登陆错误: {e}')
                raise e
            else:
                background_tasks.add_task(
                    LoginLogService.create,
                    **dict(
                        db=db,
                        request=request,
                        user_id=user.id,
                        username=obj_in.username,
                        login_time=timezone.now(),
                        status=LoginLogStatusType.success.value,
                        message='登录成功',
                    ),
                )
                data = GetLoginToken(
                    access_token=a_token.access_token,
                    access_token_expire_time=a_token.access_token_expire_time,
                    refresh_token=r_token.refresh_token,
                    refresh_token_expire_time=r_token.refresh_token_expire_time,
                    session_uuid=a_token.session_uuid,
                    user=GetSysUseInfoNoRoleDetail.model_validate(user)
                )
                return data

    @staticmethod
    async def refresh_token(*, request: Request, refresh_token: str = None) -> GetNewToken:
        if not refresh_token:
            raise ForbiddenError(message='刷新令牌已过期，请重新登录')
        try:
            user_id = jwt_decode(refresh_token).id
        except Exception:
            raise ForbiddenError(message='刷新令牌无效')
        async with async_db_session() as db:
            user = await SysUserDao.get(db, user_id)
            if not user:
                raise ForbiddenError(message='用户名或密码有误')
            elif not user.status:
                raise ForbiddenError(message='用户已被锁定, 请联系统管理员')
            new_token = await create_new_token(
                user_id=str(user.id),
                refresh_token=refresh_token,
                multi_login=user.is_multi_login,
                # Token 附加信息
                username=user.username,
                nickname=user.nickname,
                last_login_time=timezone.t_str(user.last_login_time),
                ip=request.state.ip,
                os=request.state.os,
                browser=request.state.browser,
                device_type=request.state.device,
            )
            data = GetNewToken(
                access_token=new_token.new_access_token,
                access_token_expire_time=new_token.new_access_token_expire_time,
                session_uuid=new_token.session_uuid,
            )
            return data


AuthService: AuthServices = AuthServices()
